/**
* @file   node.h
* @author sunway <sunwayforever@gmail.com>
* @date   Tue Sep 23 15:35:17 2008
*
* @brief
*
*
*/
#ifndef _NODE_H
#define _NODE_H 1
#include <iostream>
#include <vector>
#include <set>
#include <list>
#include <map>
#include <queue>
#include <sstream>
#include <algorithm>
#include <functional>
#include <cassert>

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <openssl/evp.h>
#include <time.h>
#include <sched.h>

#define ASSERT_STATIC(e) char UXXX[(e)-1]
#ifdef _USE_IPV6
#define IPSTRLEN 50
#define IPLEN 16
#else 
#define IPSTRLEN 16
#define IPLEN 4
#endif
#define IDLEN 20
#define K 20
#define ALPHA 3
#define PKTSIZE (K*(IDLEN+IPLEN+2)+2)	// todo
#define MAX_DATA_SIZE 65536
#define TIMEOUT 5
#define tRefresh 3600
#define tRepublish 86400
#define tExpire 86400
using namespace std;
namespace DHT {
    enum {TYPE_PING,TYPE_FIND_NODE,TYPE_PONG,TYPE_RESPONSE_FIND_NODE,TYPE_QUIT,TYPE_STORE,TYPE_FETCH,TYPE_RESPONSE_FETCH};
    void * listenCallBack (void * p);
    void * refreshCallBack (void * p);
    void * republishCallBack (void * p);
    
    class NodeId {
	unsigned char digest[IDLEN];
    public:
	NodeId  operator^ (const NodeId & ) const;
	NodeId () {
	}
	explicit NodeId (unsigned char * id) {
	    memcpy (digest,id,IDLEN);
	}
	explicit NodeId (int num) {
	    EVP_MD_CTX mdctx;
	    const EVP_MD *md;
	    unsigned char md_value[EVP_MAX_MD_SIZE];
	    unsigned int digest_len, i;
	    OpenSSL_add_all_digests();
	    md = EVP_get_digestbyname("sha1");
	    EVP_MD_CTX_init(&mdctx);
	    EVP_DigestInit_ex(&mdctx, md, NULL);
	    EVP_DigestUpdate(&mdctx, (char *)&num, 4);
	    EVP_DigestFinal_ex(&mdctx, digest, &digest_len);
	    EVP_MD_CTX_cleanup(&mdctx);
	}
	const unsigned char * getVal () const {
	    return digest;
	}
	int getBucket () const;
	friend ostream & operator<<(ostream &,const NodeId &);
	bool operator==(const NodeId & id2) const {
	    if (memcmp (digest,id2.digest,IDLEN)==0) {
		return true;
	    } else {
		return false;
	    }
	}
	bool operator>(const NodeId & id2) const {
	    if (memcmp (digest,id2.digest,IDLEN)>=0) {
		return true;
	    } else {
		return false;
	    }
	}
	bool operator<(const NodeId & id2) const {
	    return !operator>(id2);
	}
    };

    class Tuple {
    public:
	NodeId nodeId;
	string ip;
	uint16_t port;
	explicit Tuple (const NodeId & id,const string & ip="",uint16_t port=0)
	    :nodeId (id),ip (ip),port (port)
	    {
	    }
	Tuple () {}
	bool operator==(const Tuple & t2) const {
	    if (nodeId==t2.nodeId) {
		return true;
	    } else {
		return false;
	    }
	}
    };

    class SortCompareFn {
	NodeId nodeId;
    public:
	SortCompareFn (const NodeId & nodeId)
	    :nodeId (nodeId)
	    {
	    
	    }
	bool operator() (const Tuple & n1,const Tuple & n2) {
	    if (((n1.nodeId)^(this->nodeId)) > ((n2.nodeId)^(this->nodeId))) {
		return true;
	    } else {
		return false;
	    }
	}
    };

    class Cache {
    public:
	NodeId key;
	char * buff;
	uint16_t length;
	time_t deadLine;
	explicit Cache (const NodeId & key, const char * buff=0,uint16_t length=0)
	    :key (key),length (length)
	    {
		deadLine=time (0)+tExpire;
		this->buff=(char *)malloc (length);
		memcpy (this->buff,buff,length);
	    }
	bool operator==(const Cache & c2) const {
	    if (key==c2.key) {
		return true;
	    } else {
		return false;
	    }
	}
	~Cache () {
	    if (buff!=0) {
		free (buff);		
	    } else {
		cout<<"oops, Cache.buff is null"<<endl;
	    }
	}
	// todo: copy ctor and assign ctor should be take care
	Cache (const Cache & c)
	    :key (c.key),length (c.length),deadLine (c.deadLine)
	    {
		this->buff=(char *)malloc (length);
		memcpy (this->buff,c.buff,length);
	    }
	Cache operator= (const Cache & c) {
	    if (this==&c) {
		return *this;
	    } 
	    this->key=c.key;
	    this->length=c.length;
	    this->deadLine=c.deadLine;
	    free (this->buff);
	    this->buff=(char *)malloc (this->length);
	    memcpy (this->buff,c.buff,this->length);
	}

    };

    class Node {
	pthread_mutex_t kBucketMutex;
	Tuple myTuple;
	list<Tuple> kBucket[IDLEN*8];
	vector<Cache> cacheGet;
	// todo: need mutex on cachePut
	vector<Cache> cachePut;
	class ExTimeout:public exception{
	public:
	    const char* what() const throw() {
		return "timeout";
	    }
	};
	int localRegNode (const NodeId & nodeid,const string & ip,uint16_t port);
	int PING (const string & ip,uint16_t port) const;
	int STORE (const string & ip,uint16_t port,const NodeId &,const char * data, uint16_t nbyte) const;
	int FIND_VALUE (const string & ip,uint16_t port,const NodeId &,char * data, uint16_t nbyte) const;
	vector<Tuple> FIND_NODE (NodeId,const string & ip,uint16_t port) const throw (ExTimeout);
	vector<Tuple> localFindNode (NodeId,int n) const;
	void localDelNode (NodeId);
	void quit (NodeId);
    public:
	vector<Tuple> lookUp (NodeId nodeid) const;
	explicit Node(const NodeId & id,const string & ip,int port)
	    :myTuple (id,ip,port)
	    {
		pthread_mutex_init (&kBucketMutex,NULL);
		//localRegNode (id,ip,port);
		pthread_t tid;
		if (pthread_create (&tid,NULL,listenCallBack,(void *)this)!=0) {
		    cerr<<"error when create thread"<<endl;
		    exit (1);
		}
		// make sure listenCallBack is sched first
		sched_yield ();
		if (pthread_create (&tid,NULL,refreshCallBack,(void *)this)!=0) {
		    cerr<<"error when create thread"<<endl;
		    exit (1);
		}
		if (pthread_create (&tid,NULL,republishCallBack,(void *)this)!=0) {
		    cerr<<"error when create thread"<<endl;
		    exit (1);
		}
		
	    }
	Node () {}
	int put (const NodeId & nodeId,const char * data,uint16_t nbyte );
	int get (const NodeId & nodeId,char * data,int nbyte);
	void bootsTrap (const string & ip,uint16_t port);
	void quit ();

	friend void * listenCallBack (void * p);
	friend void * refreshCallBack (void * p);
	friend void * republishCallBack (void * p);
	friend class PredRemove;
    };
    
    class PredRemove {
	Node * np;
    public:
	PredRemove (Node * np)
	    :np (np)
	    {
	    
	    }
	bool operator()(const Tuple & t) {
	    if (np->PING (t.ip,t.port)) {
		return true;
	    } else {
		return false;
	    }
	}
    };
    bool WAIT (int sockfd);
}
#endif

// Local Variables:
// mode:C++
// End:

